package unb.wikinarua.geopartitura.service.xmpp;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.util.StringUtils;

import unb.wikinarua.geopartitura.http.HttpRequest;
import unb.wikinarua.geopartitura.util.Constants;
import unb.wikinarua.geopartitura.util.XMPPConstants;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.Settings.Secure;
import android.util.Log;

public class XMPPConnectionService extends Service {

	private ConnectionConfiguration cc = null;
	private XMPPConnection con = null;
	private Roster roster = null;
	
	private SharedPreferences pref;
	private Vibrator vibrator;
	
	private XMPPConnectionService service = this;
	
	private BroadcastReceiver connectivityReceiver;
	
	private String androidID;
	
	private static final String CERT_DIR = "/system/etc/security/";
	private static final String CERT_FILE = "cacerts.bks";
	
	@Override
	public IBinder onBind(Intent intent) {
		return (IBinder) binder;
//		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		// Get default shared preferences
		pref = PreferenceManager.getDefaultSharedPreferences(this);
		vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
		
		androidID = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
		
//		connectivityReceiver = new BroadcastReceiver() {
//			
//			@Override
//			public void onReceive(Context context, Intent intent) {
//				ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
//				
//				if (cm.getActiveNetworkInfo() != null && 
//					       cm.getActiveNetworkInfo().isConnectedOrConnecting() &&
//					       !isUserLoggedIn())
//					connectToServer();
//			}
//			
//		};
//		IntentFilter connFilter = new IntentFilter();
//		connFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
//		registerReceiver(connectivityReceiver, connFilter);
		
		// Connect to server
		connectToServer();
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		
		unregisterReceiver(connectivityReceiver);
		
		disconnectFromServer();
	}
	
	/** Handler */
	private Handler handler = new Handler() {
		
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case XMPPConstants.XMPP_CREATE_USER:
				new AddOpenfireUser().execute();
				break;
			}
		};
		
	};
	
	/** Binder */
	private final ConnectionServiceCall.Stub binder = new ConnectionServiceCall.Stub() {
		
		@Override
		public void connect(String state, String type, String mode) {
			connectToServer(state, type, mode);
		}
		
		@Override
		public void disconnect() {
			disconnectFromServer();
		}
		
		@Override
		public void login() {
		}
		
		@Override
		public void logOff() {
		}
		
		@Override
		public boolean isLoggedIn() {
			return isUserLoggedIn();
		}
		
		@Override
		public void setStatus(String state, String type, String mode) {
			setPreseceState(state, type, mode);
		}
		
		@Override
		public void sendMessage(String user, String message) {
			sendMessagePacket(user, message);
		}
		
		@Override
		public void insertAndUseMessage(String message) {
		}
		
		@Override
		public void getRoster() {
			service.sendRoster();
		}
		
		@Override
		public List<String> getLastStatusMessages() {
			return null;
		}
		
		@Override
		public void getAvatar(String user) {
		}
		
		@Override
		public void addEntry(String user, String name, List<String> groups) {
			service.addEntry(user, name, groups);
		}
		
	};

	/** Listeners */
	private PacketListener messageListener = new PacketListener() {
		
		@Override
		public void processPacket(Packet packet) {
			Message msg = (Message) packet;
			HashMap<String, String> msgInfo = getMessageInfo(msg);
			
			if (msgInfo.get(XMPPConstants.XMPP_IEXTA_BODY) != null) {
				queueIncomingMessage(msgInfo);
				
				Intent intent = new Intent(XMPPConstants.XMPP_REC_NEW_MSG);
				intent.putExtra(XMPPConstants.XMPP_IEXTA_BODY, msgInfo.get(XMPPConstants.XMPP_IEXTA_BODY));
				intent.putExtra(XMPPConstants.XMPP_IEXTA_JID, msgInfo.get(XMPPConstants.XMPP_IEXTA_JID));
				intent.putExtra(XMPPConstants.XMPP_IEXTA_NAME, msgInfo.get(XMPPConstants.XMPP_IEXTA_NAME));
				sendOrderedBroadcast(intent, null);
			}
		}
		
	};
	
	private PacketListener presenceListener = new PacketListener() {
		
		@Override
		public void processPacket(Packet packet) {
			Presence presence = (Presence) packet;
			
			String type = presence.getType().name();
			Mode mode = presence.getMode();
			
			int typeValue = getType(type);
			int modeValue = getMode(mode);
			
			Intent intent = new Intent(XMPPConstants.XMPP_REC_PRESENCE_CHANGED);
			intent.putExtra("jid", StringUtils.parseBareAddress(presence.getFrom()));
			intent.putExtra("resourceName", StringUtils.parseResource(presence.getFrom()));
			intent.putExtra("resourcePriority", presence.getPriority());
			intent.putExtra("presenceType", typeValue);
			intent.putExtra("presenceMode", modeValue);
			intent.putExtra("presenceMessage", presence.getStatus());
			sendOrderedBroadcast(intent, null);
		}
		
	};
	
	private ConnectionListener connectionListener = new ConnectionListener() {
		
		@Override
		public void reconnectionSuccessful() {
		}
		
		@Override
		public void reconnectionFailed(Exception arg0) {
		}
		
		@Override
		public void reconnectingIn(int arg0) {
		}
		
		@Override
		public void connectionClosedOnError(Exception arg0) {
			connectToServer();
		}
		
		@Override
		public void connectionClosed() {
			connectToServer();
		}
		
	};
	
	/** Server Connection */
	public void connectToServer() {
		connectToServer("", "available", "available");
	}
	
	public void connectToServer(final String state, final String type, final String mode) {
		SmackConfiguration.setPacketReplyTimeout(15000);
//		 Get user + password
//		final String username = pref.getString(XMPPConstants.XMPP_PREF_USER, null);
//		final String pwd = pref.getString(XMPPConstants.XMPP_PREF_PWD, null);
//		// Validade username
//		if ((username == null) || (username.length() == 0)) {
//			return;
//		}
		// Create ConnectionConfiguration
		cc = new ConnectionConfiguration(XMPPConstants.XMPP_SERVER_IP, XMPPConstants.XMPP_SERVER_PORT, XMPPConstants.XMPP_SERVER_NAME);
		cc.setTruststorePath(CERT_DIR + CERT_FILE);
		cc.setSecurityMode(SecurityMode.disabled);
		cc.setSASLAuthenticationEnabled(false);
		cc.setReconnectionAllowed(true);
		// Create XMPPConnection
		con = new XMPPConnection(cc);
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// Connecting to server
				try {
					con.connect();
				} catch (Exception e) {
					Log.e(Constants.TAG, e.getMessage(), e);
					sendBroadcast(new Intent(XMPPConstants.XMPP_REC_CON_FAIL));
					return;
				}
				// Adding listeners
				try {
					con.addPacketListener(messageListener, new PacketTypeFilter(Message.class));
					con.addPacketListener(presenceListener, new PacketTypeFilter(Presence.class));
					con.addConnectionListener(connectionListener);
				} catch (Exception e) {
					Log.e(Constants.TAG, e.getMessage(), e);
					sendBroadcast(new Intent(XMPPConstants.XMPP_REC_CON_FAIL));
					return;
				}
				// Login
				try {
					con.login(androidID, androidID, "Android");
//					con.login(username, pwd, "Android");
				} catch (Exception e) {
					Log.e(Constants.TAG, "Usuário inexistente - " + e.getMessage(), e);
					sendBroadcast(new Intent(XMPPConstants.XMPP_REC_CON_FAIL));
					
					handler.sendEmptyMessage(XMPPConstants.XMPP_CREATE_USER);
					return;
				}
				// Logged
				try {
					// Roster
					roster = con.getRoster();
					roster.addRosterListener(new RosterListener() {
						
						@Override
						public void presenceChanged(Presence arg0) {
						}
						
						@Override
						public void entriesUpdated(Collection<String> arg0) {
						}
						
						@Override
						public void entriesDeleted(Collection<String> arg0) {
						}
						
						@Override
						public void entriesAdded(Collection<String> arg0) {
						}
						
					});
					sendRoster();
					// Get offline messages
//					OfflineMessageManager omm = new OfflineMessageManager(con);
					// TODO Define what todo > delete or notify + save
				} catch (Exception e) {
					Log.e(Constants.TAG, e.getMessage(), e);
				}
				// Send Broadcast
				sendBroadcast(new Intent(XMPPConstants.XMPP_REC_PRESENCE_CHANGED));
				sendBroadcast(new Intent(XMPPConstants.XMPP_REC_LOGGED_IN));
			}
			
		}).start();
	}
	
	public void disconnectFromServer() {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				if (con != null) {
					try {
						con.disconnect();
					} catch (Exception e) {
						Log.e(Constants.TAG, e.getMessage(), e);
					}
				}
			}
			
		}).start();
	}
	
	/** XMPP */
	public void updateRoster() {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				if ((con != null) && (con.getUser() != null)) {
					roster = con.getRoster();
				}
			}
			
		}).start();
	}
	
	public void sendRoster() {
		/* Load roster group and entry data */
		Collection<RosterGroup> rGroups  = roster.getGroups();
		Collection<RosterEntry> entries = roster.getEntries();

		/* Send start progress information */
		Intent rosterPresenceStart = new Intent(XMPPConstants.XMPP_REC_ROSTER_PRESENCE_START);
		rosterPresenceStart.putExtra("groupsCount", rGroups.size());
		rosterPresenceStart.putExtra("entriesCount", entries.size());
		sendBroadcast(rosterPresenceStart);
		
		/* Groups */
	    for(RosterGroup grp : rGroups) {
	    	Intent rosterGroupPresence = new Intent(XMPPConstants.XMPP_REC_PRESENCE_ROSTER_GROUP);
	    	rosterGroupPresence.putExtra("groupName", grp.getName());
	    	sendBroadcast(rosterGroupPresence);
	    }


	    /* Entries */
		Presence presence;

		for(RosterEntry entry : entries) {
			presence = roster.getPresence(entry.getUser());
			
			Intent rosterPresence = new Intent(XMPPConstants.XMPP_REC_PRESENCE_ROSTER_ENTRY);
			rosterPresence.putExtra("jid", entry.getUser());
			rosterPresence.putExtra("resourceName", StringUtils.parseResource(presence.getFrom()));
			rosterPresence.putExtra("resourcePriority", presence.getPriority());
			rosterPresence.putExtra("name", entry.getName());
			rosterPresence.putExtra("presenceType", getType(presence.getType().name()));
			rosterPresence.putExtra("presenceMode", getMode(presence.getMode()));
			rosterPresence.putExtra("status", entry.getStatus() == null ? "unknown" : entry.getStatus().toString());
			rosterPresence.putExtra("msg", presence.getStatus());
			
			Collection<RosterGroup> entryGroups = entry.getGroups();
			
			ArrayList<String> groups = new ArrayList<String>();
			
			for(RosterGroup group : entryGroups) {
				groups.add(group.getName());
			}
			
			rosterPresence.putStringArrayListExtra("groups", groups);
			
			sendBroadcast(rosterPresence);
		}
		
		Intent rosterPresenceStop = new Intent(XMPPConstants.XMPP_REC_ROSTER_PRESENCE_STOP);
		sendBroadcast(rosterPresenceStop);
	}
	
	public boolean isUserLoggedIn() {
		if ((con != null) && (con.getUser() != null)) {
			return true;
		}
		return false;
	}
	
	public void sendMessagePacket(String user, String message) {
		final Message msg = new Message(user, Message.Type.chat);
		msg.setBody(message);
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				if ((con != null) && (con.getUser() != null)) {
					con.sendPacket(msg);
					logOutgoingMessage(msg.getBody(), msg.getTo());
				}
			}
			
		}).start();
	}
	
	public void addEntry(final String user, final String name, List<String> groups) {
		final String[] grps = (String[]) groups.toArray();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				if ((con != null) && (con.getUser() != null)) {
					try {
						roster.createEntry(user, name, grps);
					} catch (XMPPException e) {
						Log.e(Constants.TAG, "Can't add contact: " + user, e);
					}
				}
			}
			
		}).start();
	}
	
	public void setPreseceState(final String state, final String type, final String mode) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				if (con.getUser() != null) {
					Presence presence = new Presence(Presence.Type.valueOf(type));
					if (state != null) {
						presence.setStatus(state);
					}
					presence.setMode(Presence.Mode.valueOf(mode));
					presence.setPriority(5);
//					presence.setPriority(Integer.parseInt(prefs.getString("prefResourcePriorityKey", "5")));
					con.sendPacket(presence);
				}
			}
			
		}).start();
	}
	
	private void logOutgoingMessage(final String body, final String to) {
	}
	
	private void queueIncomingMessage(final HashMap<String,String> msgInfo) {
	}
	
	public HashMap<String, String> getMessageInfo(Message msg) {
		String userWithRes = msg.getFrom(); // plain JabberID (width resource)
		int slash = userWithRes.lastIndexOf("/"); // select index of the
													// separator of Jabber ID
													// and resource
		String resource; // only the resource name
		String user; // only the Jabber id

		// Check if there was a separator (should always be) and assign the
		// proper values
		if (slash != -1) {
			resource = userWithRes.substring(slash + 1);
			user = userWithRes.substring(0, slash);
		} else {
			resource = "unknown";
			user = userWithRes;
		}

		String userName = user; // Alias, if there is one
		RosterEntry re = roster.getEntry(user);
		if (re.getName() != null) {
			userName = re.getName();
		}

		String body = msg.getBody(); // Message
		java.util.Date date = null; // getTimestamp(packet); currently not
									// working, so ignoring it and use the
									// current time.

		if (date == null) {
			date = new java.util.Date();
		}

		long time = date.getTime();

		HashMap<String, String> list = new HashMap<String, String>();
		list.put(XMPPConstants.XMPP_IEXTA_JID, user);
		list.put(XMPPConstants.XMPP_IEXTA_RESOURCE, resource);
		list.put(XMPPConstants.XMPP_IEXTA_NAME, userName);
		list.put(XMPPConstants.XMPP_IEXTA_BODY, body);
		list.put(XMPPConstants.XMPP_IEXTA_DATE, String.valueOf(time));

		return list;
	}
	
	public int getType(String type) {
		if (type == "available") {
			return XMPPConstants.XMPP_PRESENCETYPE_AVAILABLE;
		} else if (type == "unavailable") {
			return XMPPConstants.XMPP_PRESENCETYPE_UNAVAILABLE;
		}
		return XMPPConstants.XMPP_PRESENCETYPE_NULL;
	}

	public int getMode(Mode mode) {
		if (mode == null) {
			return XMPPConstants.XMPP_PRESENCEMODE_NULL;
		} else if (mode.name() == "away") {
			return XMPPConstants.XMPP_PRESENCEMODE_AWAY;
		} else if (mode.name() == "xa") {
			return XMPPConstants.XMPP_PRESENCEMODE_XA;
		} else if (mode.name() == "chat") {
			return XMPPConstants.XMPP_PRESENCEMODE_CHAT;
		} else if (mode.name() == "dnd") {
			return XMPPConstants.XMPP_PRESENCEMODE_DND;
		}
		return XMPPConstants.XMPP_PRESENCEMODE_NULL;
	}
	
	private class AddOpenfireUser extends AsyncTask<Void, Void, Void> {
		
		@Override
		protected Void doInBackground(Void... params) {
			HttpRequest.getRequest(androidID);
			return null;
		}
		
		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			
			connectToServer();
		}
		
	}

}
